package matchthreegame.gameLogic;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Random;

import matchthreegame.extensions.MatchThreeGameRoomExtension;
import matchthreegame.gameLogic.GameChain.ChainType;

import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;

public class GameLevel {
	
	static final int NUMBER_COLUMNS = 9;
	static final int NUMBER_ROWS = 9;
	static final int NUMBER_COOKIE_TYPES = 6;
	
	public long targetScore;
	public long maximumMoves;
	public HashSet<GameSwap> possibleSwaps;
	
	private GameCookie[][] cookies = new GameCookie[NUMBER_COLUMNS][NUMBER_ROWS];
	private GameTile[][] tiles = new GameTile[NUMBER_COLUMNS][NUMBER_ROWS];
	private Random random = new Random();

	private WeakReference<MatchThreeGameRoomExtension> extension;
	
	
	public GameLevel(int level, MatchThreeGameRoomExtension extensionToSet) {
		
		this.extension = new WeakReference<MatchThreeGameRoomExtension>(extensionToSet);
		
		try {
			
			//Parse level info from JSON
			JSONObject jsonObject = GameLevel.loadJson(level);
			
			targetScore = (long)jsonObject.get("targetScore");
			maximumMoves = (long)jsonObject.get("moves");
			
			//Parse tile array 
			JSONArray tiles = (JSONArray) jsonObject.get("tiles");
			String parsedValues = new String();
			for (int row = tiles.size() - 1; row >= 0; row--) {
				
				JSONArray columns = (JSONArray)tiles.get(row);
				
				for (int col = 0; col < columns.size(); col++) {
					
					//Populate tile array (positions where could spawn cookies: 1 - can, 0 - can't)
					long valueAtPosition = (long)((JSONArray)tiles.get(row)).get(col);
					GameTile tile = new GameTile(valueAtPosition == 0);
					
					int tileRow = tiles.size() - row - 1;
					this.tiles[col][tileRow] = tile;
//					this.tiles[tileRow][col] = tile;
					
//					parsedValues = parsedValues + " " + Integer.valueOf(columns.get(col).toString());
					
				}
				
				parsedValues = parsedValues + "\n";
				
			}
			
//			this.extension.get().trace("Level: " + level + " Parsed values: " + parsedValues +
//					" Target score: " + targetScore + " Maximum moves: " + maximumMoves);
			
		} catch (Throwable e) {
			this.extension.get().trace("Error when parsing JSON" + e.getMessage());
		} 
		
		//Print tiles array in console
//		printTiles();
		
	}
	
	
	private static JSONObject loadJson(int  level) {

		JSONObject jsonObject = null;
		
		try {
			JSONParser parser=new JSONParser();
			 Object obj = parser.parse(LevelsInfo.getLevel(level));
			jsonObject = (JSONObject)obj;
			
			
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		return jsonObject;
		
	}
	
	//================================================================================
    // Create initial cookies
    //================================================================================
	
	public HashSet<GameCookie> shuffle() {
		
		HashSet<GameCookie> set;
		
		do {
//			extension.get().trace("Before create cookies: ");
			set = createInitialCookies();
//			extension.get().trace("After create cookies: ");
			detectPossibleSwaps();
//			extension.get().trace("Possible swaps: " + possibleSwaps);
			extension.get().trace(" - SHUFFLE - ");
			
		} while (this.possibleSwaps.size() == 0);
		
		return set;
		
	}
	
	private HashSet<GameCookie> createInitialCookies() {
		
		HashSet<GameCookie> set = new HashSet<GameCookie>();
		
		for (int row = 0; row < NUMBER_ROWS; row++) {
			for (int column = 0; column < NUMBER_COLUMNS; column++) {
//				extension.get().trace("Choose for tile: "+ "row:"+ row  + 
//						" col:" + column + " "+ tiles[column][row] );
				
				//Create cookies only for tiles that are not empty
				if ( tiles[column][row].isBlankTile == false ) {
					
					int cookieType;
					do {
						
						cookieType = random.nextInt(NUMBER_COOKIE_TYPES) + 1;

						//Ensure that there are no 3 cookies of the same type forming chain
						//Checking cookies bellow current and on the left of current, because
						//we are spawning them from bottom left to right ...
					} while ( (column >= 2 && 
							cookies[column - 1][row] != null  &&
							cookies[column - 1][row].cookieType == cookieType &&
							cookies[column - 2][row] != null &&
							cookies[column - 2][row].cookieType == cookieType )
							||
							(row >= 2 && 
							cookies[column][row - 1] != null && 
							cookies[column][row - 1].cookieType == cookieType &&
							cookies[column][row - 2] != null &&
							cookies[column][row - 2].cookieType == cookieType )
							);
					
//					extension.get().trace("Selected cookie type: " + cookieType);
					GameCookie cookie = createCookie(column, row, cookieType);
					set.add(cookie);
					
				}
				
			}
		}
		
//		printCookies();
		
		return set;
		
	}
	
	private GameCookie createCookie(int column, int row, int cookieType) {
		
		//Create cookie and add it to cookies array
		GameCookie cookie = new GameCookie();
		cookie.cookieType = cookieType;
		cookie.column = column;
		cookie.row = row;
		
		cookies[column][row] = cookie;
//		extension.get().trace("Current cookies: " + cookies);
		
		return cookie;
		
	}
	
	public void detectPossibleSwaps() {
		
		HashSet<GameSwap> set = new HashSet<GameSwap>();
		
		for (int row = 0; row < NUMBER_ROWS; row++) {
			for (int column = 0; column < NUMBER_COLUMNS; column++) {
				
				GameCookie cookie = cookies[column][row];
				if ( cookie != null ) {
					
					// Is it possible to swap this cookie with the one on the right?
					if ( column < NUMBER_COLUMNS - 1 ) {
						// Have a cookie in this spot? If there is blank tile, there is no cookie.
						GameCookie other = cookies[column + 1][row];
						
						if ( other != null ) {
							//Swap them
							cookies[column][row] = other;
							cookies[column + 1][row] = cookie;
							
							// Is either cookie now part of a chain?
							if ( hasChainAtPosition(column + 1, row) 
									|| hasChainAtPosition(column, row)) {
								GameSwap swap = new GameSwap();
								
								//Mark the type of chain
								if ( hasChainAtPosition(column + 1, row ) ) {
									int chainRank = getChainRankAtPosition(column + 1, row);
									swap.setChainRank(chainRank);
									swap.chainTypes.add(cookie.cookieType);
								}
								if ( hasChainAtPosition(column, row ) ) {
									int chainRank = getChainRankAtPosition(column, row);
									swap.setChainRank(chainRank);
									swap.chainTypes.add(other.cookieType);
								}
								
								swap.cookieA = cookie;
								swap.cookieB = other;
								set.add(swap);
							}
							
							// Swap them back
							cookies[column][row] = cookie;
							cookies[column + 1][row] = other;
							
						}
						
					}
					
					//Vertical check
					if ( row < NUMBER_ROWS - 1 ) {
						// Have a cookie in this spot? If there is blank tile, there is no cookie.
						GameCookie other = cookies[column][row + 1];
						
						if ( other != null ) {
							//Swap them
							cookies[column][row] = other;
							cookies[column][row + 1] = cookie;
							
							// Is either cookie now part of a chain?
							if ( hasChainAtPosition(column, row + 1) 
									|| hasChainAtPosition(column, row)) {
								GameSwap swap = new GameSwap();
								
								//Mark the type of chain
								if ( hasChainAtPosition(column, row + 1) ) {
									int chainRank = getChainRankAtPosition(column, row + 1);
									swap.setChainRank(chainRank);
									swap.chainTypes.add(cookie.cookieType);
								}
								if ( hasChainAtPosition(column, row ) ) {
									int chainRank = getChainRankAtPosition(column, row);
									swap.setChainRank(chainRank);
									swap.chainTypes.add(other.cookieType);
								}
								
								swap.cookieA = cookie;
								swap.cookieB = other;
								set.add(swap);
							}
							
							// Swap them back
							cookies[column][row] = cookie;
							cookies[column][row + 1] = other;
							
						}
						
					}
					
					
				}
				
			}
		}
		
		possibleSwaps = set;
		
	}
	
	
	//================================================================================
    // Check for chain at certain position
    //================================================================================
	
	private boolean hasChainAtPosition(int column, int row) {
		
		int cookieType = cookies[column][row].cookieType;
		
		//Check f there is the same of this cookie to left and right
		int horzLength = 1;
		for (int i = column - 1; i >= 0 && cookies[i][row] != null && 
				cookies[i][row].cookieType == cookieType; i--, horzLength++) {}
		for (int i = column + 1; i < NUMBER_COLUMNS && cookies[i][row] != null &&
				cookies[i][row].cookieType == cookieType; i++, horzLength++) {}
		
		if ( horzLength >= 3 ) return true;
		
		//Check f there is the same of this cookie above and below
		int vertLength = 1;
		for (int i = row - 1; i >= 0 && cookies[column][i] != null && 
				cookies[column][i].cookieType == cookieType; i--, vertLength++) {}
		for (int i = row + 1; i < NUMBER_ROWS && cookies[column][i] != null && 
				cookies[column][i].cookieType == cookieType; i++, vertLength++) {}
		
		return vertLength >= 3;
		
	}
	
	private int getChainRankAtPosition(int column, int row) {
		
		int cookieType = cookies[column][row].cookieType;
		
		//Check f there is the same of this cookie to left and right
		int horzLength = 1;
		for (int i = column - 1; i >= 0 && cookies[i][row] != null && 
				cookies[i][row].cookieType == cookieType; i--, horzLength++) {}
		for (int i = column + 1; i < NUMBER_COLUMNS && cookies[i][row] != null &&
				cookies[i][row].cookieType == cookieType; i++, horzLength++) {}
        
		//Check f there is the same of this cookie above and below
		int vertLength = 1;
		for (int i = row - 1; i >= 0 && cookies[column][i] != null && 
				cookies[column][i].cookieType == cookieType; i--, vertLength++) {}
		for (int i = row + 1; i < NUMBER_ROWS && cookies[column][i] != null && 
				cookies[column][i].cookieType == cookieType; i++, vertLength++) {}
		
		return Math.max(horzLength, vertLength);
		
	}
	
	public boolean isPossibleSwap(GameSwap swap) {
		
		return possibleSwaps.contains(swap);
		
	}
	
	//================================================================================
    // Perform swap
    //================================================================================
	
	public void performSwap(GameSwap swap) {
		
		if ( possibleSwaps.contains(swap)) {
//			extension.get().trace("Possible Swap");
			
			int columnA = swap.cookieA.column;
			int rowA = swap.cookieA.row;
			int columnB = swap.cookieB.column;
			int rowB = swap.cookieB.row;
			
			cookies[columnA][rowA] = swap.cookieB;
		    swap.cookieB.column = columnA;
		    swap.cookieB.row = rowA;
		    
		    cookies[columnB][rowB] = swap.cookieA;
		    swap.cookieA.column = columnB;
		    swap.cookieA.row = rowB;
			
		} else {
			extension.get().trace("Not possible swap send !! Someone tries to hack " + possibleSwaps);
		}
		
	}
	
	//================================================================================
	// Remove matches
    //================================================================================
	
	public HashSet<GameChain> removeMatches() {
		
		HashSet<GameChain> horizontalChains = detectHorizontalMatches();
		HashSet<GameChain> verticalChains = detectVerticalMatches();
		removeCookies(horizontalChains);
		removeCookies(verticalChains);
		
		//Merge two sets
		horizontalChains.addAll(verticalChains);
		
		return horizontalChains;
		
	}
	
	private void removeCookies(HashSet<GameChain> chains) {
		
		for (GameChain chain : chains) {
			
			for (GameCookie cookie : chain.cookies) {
				cookies[cookie.column][cookie.row] = null;
			}
			
		}
		
	}
	
	private HashSet<GameChain> detectHorizontalMatches() {
		
		HashSet<GameChain> set = new HashSet<GameChain>();
		
		for (int row = 0; row < NUMBER_ROWS; row++) {
			for (int column = 0; column < NUMBER_COLUMNS - 2; ) {
				
				if ( cookies[column][row] != null ) {
					
					int matchType = cookies[column][row].cookieType;
					
					if (cookies[column + 1][row] != null && cookies[column + 1][row].cookieType == matchType
		                    && cookies[column + 2][row] != null && cookies[column + 2][row].cookieType == matchType) {
		                    
		                    GameChain chain = new GameChain();
		                    chain.chainType = ChainType.ChainTypeHorizontal;
		                    do {
		                        chain.addCookie(cookies[column][row]);
		                        column += 1;
		                    }
		                    while (column < NUMBER_COLUMNS && cookies[column][row] != null &&
		                    		cookies[column][row].cookieType == matchType);
		                    
		                    set.add(chain);
		                    continue;
		                }
					
				}
				
				column += 1;
				
			}
		}
		
//		extension.get().trace("Horizontal matches: " + set.size());
		
		return set;
		
	}
	
	private HashSet<GameChain> detectVerticalMatches() {
		
		HashSet<GameChain> set = new HashSet<GameChain>();
		
		for (int column = 0; column < NUMBER_COLUMNS; column++) {
			for (int row = 0; row < NUMBER_ROWS - 2; ) {
				
				if ( cookies[column][row] != null ) {
					
					int matchType = cookies[column][row].cookieType;
					
					if (cookies[column][row + 1] != null && cookies[column][row + 1].cookieType == matchType
		                    && cookies[column][row + 2] != null && cookies[column][row + 2].cookieType == matchType) {
		                    
		                    GameChain chain = new GameChain();
		                    chain.chainType = ChainType.ChainTypeVertical;
		                    do {
		                        chain.addCookie(cookies[column][row]);
		                        row += 1;
		                    }
		                    while (row < NUMBER_ROWS && cookies[column][row] != null && 
		                    		cookies[column][row].cookieType == matchType);
		                    
		                    set.add(chain);
		                    continue;
		                }
					
				}
				
				row += 1;
				
			}
		}
		
//		extension.get().trace("Vertical matches: " + set.size());
		
		return set;
		
	}
	
	//================================================================================
    // Fill holes in columns
    //================================================================================
	
	public ArrayList<ArrayList<GameCookie>> fillHoles() {
		
		ArrayList<ArrayList<GameCookie>> columns = new ArrayList<ArrayList<GameCookie>>();
		
		for (int column = 0; column < NUMBER_COLUMNS; column++) {
			
			ArrayList<GameCookie> array = null;
			for (int row = 0; row < NUMBER_ROWS; row++) {
				
				if ( tiles[column][row].isBlankTile == false && cookies[column][row] == null ) {
					
					for (int lookup = row + 1; lookup < NUMBER_ROWS; lookup++) {
						GameCookie cookie = cookies[column][lookup];
						if ( cookie != null ) {
							cookies[column][lookup] = null;
							cookies[column][row] = cookie;
							cookie.row = row;
							
							if ( array == null ) {
								array = new ArrayList<GameCookie>();
								columns.add(array);
							}
							array.add(cookie);
							break;
							
						}
					}
					
				}
				
			}
			
		}
		
		return columns;
		
	}
	
	//================================================================================
    // Spawn new cookies
    //================================================================================
	
	public ArrayList<ArrayList<GameCookie>> topUpCookies() {
		
		ArrayList<ArrayList<GameCookie>> columns = new ArrayList<ArrayList<GameCookie>>();
	    
	    int cookieType = 0;
	    
	    for (int column = 0; column < NUMBER_COLUMNS; column++) {
	        
	        ArrayList<GameCookie> array = null;
	        
	        for (int row = NUMBER_ROWS - 1; row >= 0 && cookies[column][row] == null; row--) {
	            
	            if (tiles[column][row].isBlankTile == false ) {
	                
	                int newCookieType;
	                do {
	                    newCookieType = random.nextInt(NUMBER_COOKIE_TYPES) + 1;
	                } while (newCookieType == cookieType);
	                cookieType = newCookieType;
	                
	                GameCookie cookie = new GameCookie(column, row, cookieType);
	                cookies[column][row] = cookie;
	                
	                if (array == null) {
	                    array = new  ArrayList<GameCookie>();
	                    columns.add(array);
	                }
	                array.add(cookie);
	            }
	            
	        }
	        
	    }
	    
	    return columns;

	}
	
	//================================================================================
    // Util's
    //================================================================================
	
	private void printTiles() {
		
		String printTiles = new String();
		
		try {
			for (int row = NUMBER_ROWS - 1; row >= 0; row--) {
				
				printTiles+= "\n" + "Row: " + row + " --- ";
				
				for (int col = 0; col < NUMBER_COLUMNS ; col++) {
					
					printTiles+= " " + tiles[col][row];
					
				}
				
			}
		} catch (Exception e) {
			this.extension.get().trace("Error when printing tiles array" + e.getMessage());
		}
		
		this.extension.get().trace(printTiles);
		
	}
	
	public void printCookies() {
		
		String printTiles = new String();
		
		try {
			for (int row = NUMBER_ROWS - 1; row >= 0; row--) {
				
				printTiles+= "\n" + "Row: " + row + " --- ";
				
				for (int col = 0; col < NUMBER_COLUMNS ; col++) {
					
					if ( cookies[col][row] == null ) {
						printTiles+= "_";
					} else {
						printTiles+= cookies[col][row].cookieType;
					}
					
				}
				
			}
		} catch (Exception e) {
			this.extension.get().trace("Error when printing cookies array" + e.getMessage());
		}
		
		this.extension.get().trace(printTiles);
		
	}
	
	
	public HashSet<GameCookie> getCurrentBoard() {
		
		HashSet<GameCookie> set = new HashSet<GameCookie>();
		
		for (int row = 0; row < NUMBER_ROWS; row++) {
			for (int column = 0; column < NUMBER_COLUMNS; column++) {
				
				// Add cookies only if exist
				if ( cookies[column][row] != null ) {
					
					set.add(cookies[column][row]);
					
				}
			}
		}
		
		return set;
		
	}
	
	
//	private String readFile(String fileName) throws IOException {
//		
//	    BufferedReader br = new BufferedReader(new FileReader(fileName));
//	    try {
//	        StringBuilder sb = new StringBuilder();
//	        String line = br.readLine();
//
//	        while (line != null) {
//	            sb.append(line);
//	            sb.append("\n");
//	            line = br.readLine();
//	        }
//	        return sb.toString();
//	    } finally {
//	        br.close();
//	    }
//	    
//	}
	
}
